Screen Reader Testing Procedures
**Purpose:** Verify NVDA (Windows) and VoiceOver (macOS) compatibility for WCAG 2.1 AA compliance
**Schedule:** Quarterly audits, before major releases, when introducing new UI patterns
**Tools:**
- **NVDA** (Windows) - Free, open source screen reader
- **VoiceOver** (macOS) - Built-in screen reader
**Why This Matters:**
Automated accessibility tools catch ~70% of WCAG violations, but cannot verify:
- Are announcements clear and helpful?
- Is navigation efficient?
- Do dynamic updates announce properly?
- Is the screen reader experience actually usable?
---
NVDA Testing (Windows)
Setup
- **Install NVDA:**
- Download from https://www.nvaccess.org/download/
- Install with default settings
- Launch NVDA (Ctrl+Alt+N, or from Start Menu)
- **Configure Settings:**
- Use default speech settings
- Ensure "Speech Mode" is set to "Talk"
- Set "Braille" to "No braille" (unless using braille display)
- **Choose Browser:**
- **Firefox** (recommended) - Best NVDA compatibility
- **Chrome** (acceptable) - Good support, but Firefox preferred
- **Edge** (acceptable) - Generally good support
- **Test Environment:**
- Test on Windows 10 or 11
- Use external speakers or headphones
- Ensure volume is audible but not uncomfortable
Key NVDA Commands
**Global Commands (work in any application):**
| Command | Action | Notes |
|---|---|---|
Ctrl+Alt+N | Launch NVDA | Starts screen reader |
NVDA+Q | Quit NVDA | Prompts to confirm exit |
NVDA+Ctrl+S | Toggle speech | Silence/resume speech |
NVDA+Ctrl+R | Restart NVDA | Reload NVDA settings |
**Navigation Commands (in browser):**
| Command | Action | Notes |
|---|---|---|
NVDA+Down | Read all | Start reading from current position |
NVDA+Up | Previous line | Move focus and read previous line |
NVDA+K | Read current line | Read current focused line |
NVDA+B | Read all from top | Start reading from page beginning |
NVDA+L | Read current line (alternate) | Different annunciation |
**Heading Navigation:**
| Command | Action | Notes |
|---|---|---|
H | Next heading | Jump to next heading at any level |
Shift+H | Previous heading | Jump to previous heading |
1-6 | Jump to heading level | Press number for specific level |
NVDA+F7 | Elements list | Shows all headings, links, landmarks |
**Landmark Navigation:**
| Command | Action | Notes |
|---|---|---|
NVDA+D | Landmarks list | Shows all ARIA landmarks |
D | Next landmark | Jump to next landmark |
Shift+D | Previous landmark | Jump to previous landmark |
**Table Navigation:**
| Command | Action | Notes |
|---|---|---|
T | Next table | Jump to next table |
Shift+T | Previous table | Jump to previous table |
Ctrl+Alt+Arrows | Navigate table cells | Move between cells |
NVDA+F5 | Read table headers | Announce column/row headers |
**Form Navigation:**
| Command | Action | Notes |
|---|---|---|
F | Next form field | Jump to next input |
Shift+F | Previous form field | Jump to previous input |
NVDA+Space | Activate | Activate button/checkbox |
Enter | Submit | Submit form or activate link |
Space | Toggle | Toggle checkbox/radio button |
**Focus and Object Navigation:**
| Command | Action | Notes |
|---|---|---|
Tab | Next focusable | Move to next focusable element |
Shift+Tab | Previous focusable | Move to previous focusable |
NVDA+F8 | Current object | Get details about focused object |
NVDA+Numpad5 | Read object | Read current object properties |
NVDA+Numpad8 | Read object and type | Object type and state |
**Note:** NVDA key = Insert key (may be CapsLock on some keyboards)
Test Scenarios for NVDA
1. Page Load
**Test Steps:**
- Navigate to page URL
- Press
NVDA+Bto read from top - Listen to announcements
**Expected Results:**
- [ ] Page title announced immediately
- [ ] Main content landmark announced
- [ ] Number of landmarks announced (if configured)
- [ ] Heading level 1 (page title) announced
- [ ] Language attribute announced (if non-English)
**Common Issues:**
- ❌ No page title → Add
<title>Page Name</title>to<head> - ❌ No landmarks → Add
<main>,<nav>,<header>,<footer>regions - ❌ No h1 → Add single
<h1>for page title
2. Navigation by Headings
**Test Steps:**
- Press
Hto jump through headings - Press
1-6to jump to specific heading levels - Press
NVDA+F7to open elements list, then choose "Headings"
**Expected Results:**
- [ ] Heading level announced correctly (h1, h2, h3, etc.)
- [ ] Heading text announced clearly
- [ ] Heading hierarchy makes sense (no skipped levels)
- [ ] Headings provide good navigation structure
**Common Issues:**
- ❌ Skipped heading levels (h1 → h3) → Add missing h2
- ❌ Headings used for styling → Use CSS classes, not heading tags
- ❌ Vague headings → Use descriptive headings
3. Navigation by Landmarks
**Test Steps:**
- Press
NVDA+Dto open landmarks list - Choose landmark from list
- Press
DandShift+Dto cycle through landmarks
**Expected Results:**
- [ ] All major regions have landmarks (banner, nav, main, footer)
- [ ] Landmark names are descriptive (not "region")
- [ ] Multiple landmarks of same type have labels (e.g., "Primary navigation", "Footer navigation")
- [ ] Can jump directly to any major region
**Common Issues:**
- ❌ Missing landmarks → Add
<nav>,<main>,<aside>with aria-label - ❌ Generic "region" landmarks → Add aria-label to identify purpose
- ❌ Nested landmarks → Flatten structure where possible
4. Forms
**Test Steps:**
- Navigate to form (using landmarks or headings)
- Press
Fto jump between form fields - Fill out form using keyboard only
- Submit form
**Expected Results:**
- [ ] Each input has visible label announced before input
- [ ] Required fields indicated ("required", "star", etc.)
- [ ] Placeholder text is NOT announced as label (placeholder ≠ label)
- [ ] Error messages announced immediately after invalid submission
- [ ] Validation feedback provided (inline errors, aria-invalid)
- [ ] Form can be completed without mouse
**Common Issues:**
- ❌ Missing labels → Add
<label for="id">Label Text</label>+id="id"on input - ❌ Placeholder as label → Add proper label element
- ❌ Errors not announced → Add
role="alert"oraria-live="polite"to error messages - ❌ Required not indicated → Add
aria-required="true"+ visual indicator
5. Dynamic Content (Modals, Toasts, Live Updates)
**Test Steps:**
- Open modal (trigger action)
- Listen to announcement
- Close modal
- Observe focus behavior
- Trigger toast/notification
- Trigger dynamic content update (e.g., search results)
**Expected Results:**
- [ ] Modal open announced ("dialog appeared", or custom message)
- [ ] Focus moves into modal (trap focus)
- [ ] Modal close announced
- [ ] Focus returns to trigger element after close
- [ ] Toast notifications announced (aria-live region)
- [ ] Loading states announced ("loading...", "loaded")
- [ ] New items in lists announced
**Common Issues:**
- ❌ Modal not announced → Add
role="dialog"+aria-modal="true"+aria-labelledby - ❌ Focus not trapped → Implement focus trap in modal
- ❌ Focus not returned → Restore focus to trigger on close
- ❌ Toast not announced → Add
role="status"orrole="alert"+aria-live - ❌ Loading not announced → Add
aria-live="polite"to loading indicator
6. Tables
**Test Steps:**
- Navigate to table (press
T) - Press
Ctrl+Alt+Arrowsto navigate cells - Press
NVDA+F5to read headers
**Expected Results:**
- [ ] Table purpose announced ("table with X columns")
- [ ] Column/row headers announced when navigating
- [ ] Cell coordinates announced (optional but helpful)
- [ ] Can navigate entire table with keyboard
- [ ] Data tables have
<th>elements withscopeattributes
**Common Issues:**
- ❌ No headers → Add
<th scope="col">or<th scope="row"> - ❌ Layout table announced as data → Add
role="presentation"to layout tables - ❌ Nested tables → Flatten to single table if possible
7. Links and Buttons
**Test Steps:**
- Navigate through page using
Tab - Listen to link/button announcements
- Visit
NVDA+F7→ "Links" to see all links
**Expected Results:**
- [ ] Link destination is clear from text or aria-label
- [ ] "Click here" links avoided (use descriptive text)
- [ ] Icon-only buttons have aria-label
- [ ] Button purpose announced (e.g., "Submit button", "Close button")
- [ ] Link vs button distinguished appropriately
**Common Issues:**
- ❌ "Click here" links → Use descriptive link text: "Download pricing PDF"
- ❌ Icon button no label → Add
aria-label="Close dialog"to icon button - ❌ Link used as button → Use
<button>for actions,<a>for navigation
8. Keyboard Accessibility
**Test Steps:**
- Complete entire critical user path using
Tabonly - Try to escape from modals with
Escape - Try to close dropdowns with
Escape - Verify focus visible at all times
**Expected Results:**
- [ ] All interactive elements reachable via
Tab - [ ]
Taborder matches visual order (left-to-right, top-to-bottom) - [ ] Focus visible on all interactive elements (2px+ outline)
- [ ]
Escapekey closes modals, dropdowns, menus - [ ]
Enter/Spaceactivate buttons, links, checkboxes - [ ] Arrow keys work in dropdowns, menus, lists
**Common Issues:**
- ❌ Focus not visible → Add
:focus { outline: 2px solid blue; } - ❌ Wrong tab order → Ensure DOM order matches visual order
- ❌ Keyboard trap → Ensure Escape works, focus can exit
- ❌ Skip link invisible → Make skip link visible on
:focus
---
VoiceOver Testing (macOS)
Setup
- **Enable VoiceOver:**
- Press
Cmd+F5to toggle VoiceOver on/off - Or: System Settings → Accessibility → VoiceOver → Enable
- **Configure Settings:**
- VoiceOver Utility → Speech → Default voice and rate
- Set verbosity to "Medium" (balanced detail)
- Enable "Navigation verbosity" → "High" (more detail)
- **Choose Browser:**
- **Safari** (recommended) - Best VoiceOver integration
- **Chrome** (acceptable) - Generally good support
- **Firefox** (not recommended) - Poor VoiceOver support
- **Test Environment:**
- Test on macOS 13 (Ventura) or later
- Use external speakers or headphones
- Close other apps that might interfere
Key VoiceOver Commands
**Global Commands (work in any application):**
| Command | Action | Notes |
|---|---|---|
Cmd+F5 | Toggle VoiceOver | Enable/disable screen reader |
Cmd+Fn+F5 | Toggle VoiceOver (on laptops) | Alternate command |
VO+; | Stop speech | Silence current speech |
VO+Cmd+; | Restart VoiceOver | Reload VoiceOver settings |
VO+Cmd+\` | Open VoiceOver Utility | Configure settings |
**Note:** VO = Ctrl+Option (hold both keys together)
**Navigation Commands (in browser):**
| Command | Action | Notes |
|---|---|---|
VO+Right | Next item | Move to next element |
VO+Left | Previous item | Move to previous element |
VO+Down | Interact with element | Enter "interact" mode |
VO+Up | Stop interaction | Exit "interact" mode |
VO+Shift+Down | Read all | Start reading from current position |
VO+Shift+Right | Read next sentence | Read sentence by sentence |
**Heading Navigation:**
| Command | Action | Notes |
|---|---|---|
VO+H | Next heading | Jump to next heading |
VO+Shift+H | Previous heading | Jump to previous heading |
VO+Cmd+H | Jump to heading level | Choose heading level 1-6 |
**Rotor Navigation (most important VoiceOver feature):**
| Command | Action | Notes |
|---|---|---|
VO+U | Open rotor | Shows navigation menu |
VO+U then H | Headings in rotor | Jump by headings |
VO+U then L | Landmarks in rotor | Jump by landmarks |
VO+U then I | Items in rotor | All links, headings, etc. |
VO+I | Item chooser | Searchable list of all items |
Left/Right in rotor | Navigate rotor options | Cycle through rotor types |
**Table Navigation:**
| Command | Action | Notes |
|---|---|---|
VO+] | Next table | Jump to next table |
VO+[ | Previous table | Jump to previous table |
VO+Cmd+] | Table row | Navigate table by rows |
VO+Down (in table) | Interact with table | Enter table navigation mode |
**Form Navigation:**
| Command | Action | Notes |
|---|---|---|
VO+Cmd+J | Next form field | Jump to next input |
Tab | Next focusable | Move to next focusable element |
Shift+Tab | Previous focusable | Move to previous focusable |
Space | Activate button | Toggle checkbox, activate button |
Enter | Activate link/button | Follow link, submit form |
**Web Item-Specific Commands:**
| Command | Action | Notes |
|---|---|---|
VO+Cmd+X | Next link | Jump to next link |
VO+Shift+Cmd+X | Previous link | Jump to previous link |
VO+C | Next visited link | Jump to next visited link |
VO+Shift+C | Previous visited link | Jump to previous visited link |
Test Scenarios for VoiceOver
1. Page Load
**Test Steps:**
- Navigate to page URL in Safari
- Listen to initial announcement
**Expected Results:**
- [ ] Page title announced
- [ ] "Web content" announced (entire page region)
- [ ] Number of landmarks (if configured)
- [ ] Heading structure available in rotor (
VO+U→ "Headings") - [ ] Can navigate by landmarks via rotor
**Common Issues:**
- ❌ No page title → Add
<title>Page Name</title> - ❌ No landmarks → Add
<main>,<nav>,<header>,<footer>with aria-label - ❌ No headings → Add heading structure for navigation
2. Navigation with Rotor
**Test Steps:**
- Press
VO+Uto open rotor - Use Left/Right arrows to choose "Headings"
- Use Up/Down arrows to navigate headings
- Press
VO+Uagain, choose "Landmarks" - Navigate landmarks
**Expected Results:**
- [ ] Rotor shows "Headings" option
- [ ] Rotor shows "Landmarks" option
- [ ] Rotor shows "Links" option
- [ ] Heading levels announced in rotor
- [ ] Can jump directly to any heading or landmark
- [ ] Landmarks have descriptive names (not "region")
**Common Issues:**
- ❌ Rotor missing options → Ensure semantic HTML (headings, landmarks)
- ❌ Vague landmark names → Add
aria-label="Main content"to<main> - ❌ No heading structure → Add proper heading hierarchy
3. Forms
**Test Steps:**
- Navigate to form using rotor or headings
- Press
VO+Cmd+Jto jump between form fields - Fill out form (interact with each input)
- Submit form
**Expected Results:**
- [ ] Label announced BEFORE input (when landing on input)
- [ ] "Edit text" or "checkbox" announced (input type)
- [ ] Required field indicated ("required", "star")
- [ ] Placeholder NOT announced as label (it's just a hint)
- [ ] Error messages announced immediately after appearing
- [ ] Validation feedback provided
**Common Issues:**
- ❌ No label announced → Add
<label for="id">+id="id"on input - ❌ Placeholder as label → Add proper label, keep placeholder as hint only
- ❌ Errors not announced → Add
role="alert"to error container +aria-live - ❌ Required not indicated → Add
aria-required="true"+ visual "*"
4. Dynamic Content (Modals, Toasts, Live Updates)
**Test Steps:**
- Trigger modal open (button, link)
- Listen to announcement
- Interact with modal (
VO+Down) - Close modal
- Observe focus behavior
- Trigger toast/notification
- Trigger dynamic content update
**Expected Results:**
- [ ] Modal announced ("dialog appeared", "window appeared")
- [ ] Focus moves to modal title or first focusable element
- [ ] Focus trapped in modal (can't tab outside)
- [ ] Modal close announced
- [ ] Focus returns to trigger button after close
- [ ] Toast notifications announced
- [ ] Loading states announced
- [ ] New list items announced
**Common Issues:**
- ❌ Modal not announced → Add
role="dialog"+aria-modal="true"+aria-labelledby - ❌ Focus not trapped → Implement focus trap (JavaScript required)
- ❌ Focus not restored → Save trigger element, restore focus on close
- ❌ Toast not announced → Add
role="status"orrole="alert"+aria-live="polite" - ❌ Loading not announced → Add
aria-live="polite"to loading indicator
5. Tables
**Test Steps:**
- Navigate to table (
VO+]) - Interact with table (
VO+Down) - Navigate cells with arrow keys
- Listen to header announcements
**Expected Results:**
- [ ] Table announced as table
- [ ] Table dimensions announced (rows, columns)
- [ ] Column/row headers announced when navigating
- [ ] Cell coordinates announced (optional but helpful)
- [ ] Can navigate entire table with arrow keys
- [ ] Sorting/filtering controls announced
**Common Issues:**
- ❌ No headers → Add
<th scope="col">or<th scope="row"> - ❌ Layout table → Add
role="presentation"to layout tables - ❌ Nested tables → Flatten to single table structure
- ❌ Coordinates missing → Add proper header associations
6. Links and Buttons
**Test Steps:**
- Navigate through page with
VO+Right - Listen to link/button announcements
- Open rotor (
VO+U) → "Links" - Navigate through all links
**Expected Results:**
- [ ] Link text announced clearly
- [ ] "Link" announced after text (distinguish from buttons)
- [ ] "Button" announced for button elements
- [ ] Icon-only buttons have aria-label announced
- [ ] Visited links indicated (if configured)
- [ ] Link destination is clear from text
**Common Issues:**
- ❌ "Click here" links → Use descriptive text: "View pricing"
- ❌ Icon button no label → Add
aria-label="Close dialog" - ❌ Ambiguous link text → Use descriptive destination
- ❌ Button used as link → Use
<a>for navigation,<button>for actions
7. Keyboard Accessibility
**Test Steps:**
- Navigate entire page using
Tabonly - Close modals with
Escape - Close dropdowns with
Escape - Verify focus visible at all times
**Expected Results:**
- [ ] All interactive elements reachable via
Tab - [ ]
Taborder matches visual order - [ ] Focus indicator visible (thick outline, background change)
- [ ]
Escapecloses modals, dropdowns, menus - [ ]
Enter/Spaceactivate buttons, links - [ ] Arrow keys work in dropdowns, menus, lists
**Common Issues:**
- ❌ Focus not visible → Add
:focus { outline: 3px solid blue; outline-offset: 2px; } - ❌ Wrong tab order → Fix DOM order to match visual order
- ❌ Keyboard trap → Ensure all interactive elements exitable with
Escape - ❌ Skip link hidden → Make skip link visible on
:focus
---
Critical User Paths to Test
Authentication
**Login Form:**
- [ ] Email input label announced ("Email input")
- [ ] Password input label announced ("Password input")
- [ ] Submit button announced ("Log in button")
- [ ] Validation errors announced ("Invalid email")
- [ ] "Forgot password?" link destination clear
- [ ] Can complete login with keyboard only
**Password Reset:**
- [ ] Instructions announced clearly
- [ ] Email input label announced
- [ ] Submit button announced
- [ ] Success message announced ("Email sent")
- [ ] Back to login link available
**Signup Form:**
- [ ] All fields labeled (name, email, password, etc.)
- [ ] Required fields indicated
- [ ] Password strength announced (if implemented)
- [ ] Terms of service checkbox label clear
- [ ] Submit button announced
- [ ] Validation errors announced per field
Agent Management
**Agent List (Table):**
- [ ] Table purpose announced ("Agents table, 5 columns")
- [ ] Column headers announced (Name, Status, Actions)
- [ ] Row navigation works (up/down arrows)
- [ ] Can navigate to agent details
- [ ] Action buttons labeled ("Edit", "Delete")
- [ ] Sortable columns indicate sort direction
**Agent Creation Form:**
- [ ] Multi-step form progress announced ("Step 1 of 3")
- [ ] Each step heading announced
- [ ] Form fields labeled clearly
- [ ] Validation feedback provided
- [ ] Back/Next navigation buttons labeled
- [ ] Can complete form with keyboard only
**Agent Configuration:**
- [ ] Skill selection checkboxes labeled
- [ ] Toggle switches announced ("enabled", "disabled")
- [ ] Save/Cancel buttons labeled
- [ ] Configuration changes announced
Canvas/Skills Marketplace
**Marketplace (Cards):**
- [ ] Card structure clear (heading, description, install button)
- [ ] Install buttons labeled ("Install skill", "Learn more")
- [ ] Search input label announced
- [ ] Filter controls announced
- [ ] Can navigate marketplace with keyboard
**Skill Install Confirmation:**
- [ ] Modal announced ("Install skill dialog")
- [ ] Skill name announced
- [ ] Confirm button labeled ("Install", "Cancel")
- [ ] Focus returns to trigger after close
Billing
**Usage Data Table:**
- [ ] Table headers announced (Date, Usage, Cost)
- [ ] Data cells readable
- [ ] Sortable columns indicate direction
- [ ] Pagination controls labeled
- [ ] Export button labeled ("Export CSV")
**Pricing Comparison:**
- [ ] Plan names announced (Free, Solo, Team)
- [ ] Feature lists clear (checkmarks, x marks)
- [ ] Pricing differences clear
- [ ] Subscribe buttons labeled
- [ ] Can compare plans easily
Admin
**Tenant List (Table):**
- [ ] Table headers announced
- [ ] Row actions labeled ("View", "Edit", "Suspend")
- [ ] Status indicators announced ("Active", "Suspended")
- [ ] Can navigate and perform actions with keyboard
**User Management:**
- [ ] User list table navigable
- [ ] Status changes announced
- [ ] Confirmation dialogs for destructive actions
- [ ] Error messages announced
---
Edge Cases to Verify
Custom Components
**Custom Dropdowns (Select alternatives):**
- [ ] Expand/collapse announced ("menu expanded", "menu collapsed")
- [ ] Current value announced
- [ ] Options navigable with arrow keys
- [ ] Selection announced
- [ ]
Escapecloses dropdown - [ ] Focus management correct
**Custom Checkboxes:**
- [ ] Checked state announced ("checked", "not checked")
- [ ] Label announced before checkbox
- [ ]
Spacetoggles checkbox - [ ] Visual state matches announced state
**Toast Notifications:**
- [ ] Toast announced automatically (live region)
- [ ] Toast message clear
- [ ] Dismiss button labeled (if present)
- [ ] Auto-dismiss after timeout (optional)
- [ ] Multiple toasts announced sequentially
**Auto-suggest / Autocomplete:**
- [ ] Input label announced
- [ ] Options announced when typing
- [ ] Number of options announced ("5 suggestions available")
- [ ] Can navigate options with arrow keys
- [ ] Selection announced
Keyboard + Screen Reader
**Tab Order:**
- [ ] Tab order matches visual order (left-to-right, top-to-bottom)
- [ ] Skip links work (jump to main content)
- [ ] Focus visible at all times
- [ ] Focus announcements accurate
- [ ] No keyboard traps (can't exit component)
**Escape Key Behavior:**
- [ ] Modals close with
Escape - [ ] Dropdowns/menus close with
Escape - [ ] Focus returns to trigger after close
- [ ]
Escapeannounced appropriately
**Focus Management:**
- [ ] Focus moves to modal when opened
- [ ] Focus trapped in modal (can't tab out)
- [ ] Focus returns to trigger after close
- [ ] Focus moves to error message after validation fails
- [ ] Focus moves to new content after navigation
Dynamic Updates
**Loading States:**
- [ ] "Loading" announced when fetching data
- [ ] "Loaded" announced when complete
- [ ] Spinner announced (if visible)
- [ ] Progress bars updated (live region)
**New List Items:**
- [ ] New items in list announced
- [ ] Number of new items announced
- [ ] Can navigate to new items
- [ ] Updates don't interrupt current task
**Live Chat / Notifications:**
- [ ] New messages announced (live region)
- [ ] Sender announced
- [ ] Message content announced
- [ ] Notification sound (optional)
---
Testing Checklist
Quarterly Audit (Full Manual Testing)
**Setup:**
- [ ] Test on Windows with NVDA + Firefox
- [ ] Test on macOS with VoiceOver + Safari
- [ ] Use external speakers/headphones
- [ ] Allow 4-6 hours for full audit
**Execution:**
**NVDA (Windows):**
- [ ] Run through all NVDA test scenarios (above)
- [ ] Test all critical user paths
- [ ] Test edge cases (custom components, dynamic content)
- [ ] Document all violations in accessibility tracker
**VoiceOver (macOS):**
- [ ] Run through all VoiceOver test scenarios (above)
- [ ] Test all critical user paths
- [ ] Test edge cases (custom components, dynamic content)
- [ ] Document all violations in accessibility tracker
**Post-Testing:**
- [ ] Compile violation report (see
VIOLATION_REPORTING.md) - [ ] Prioritize: critical > serious > moderate
- [ ] Create GitHub issues with
a11ylabel - [ ] Track remediation progress
- [ ] Schedule re-testing after fixes
Before Major Release
**Timeline:** 1-2 weeks before release
**Duration:** 2-4 hours
- [ ] Run automated suite first (
npm run test:a11y:full) - [ ] Generate violation report (
npm run a11y:report) - [ ] Test new UI features with NVDA + VoiceOver
- [ ] Verify previous violations fixed
- [ ] Document new violations
- [ ] Fix critical/serious violations before release
- [ ] Schedule moderate violations for next sprint
When Introducing New UI Patterns
**Trigger:** New component, new layout, new interaction pattern
**Duration:** 1-2 hours
- [ ] Test new component with NVDA
- [ ] Test new component with VoiceOver
- [ ] Verify keyboard navigation works
- [ ] Verify focus management correct
- [ ] Add jest-axe component test if missing
- [ ] Add E2E a11y test if critical path
- [ ] Document any violations found
---
Common Issues and Fixes
| Issue | Cause | Fix | Example |
|---|---|---|---|
| Not announced | Missing aria-live | Add aria-live="polite" | <div aria-live="polite">{message}</div> |
| No label | Missing for/id | Add label with for attribute | <label for="email">Email</label><input id="email"> |
| Confusing navigation | Nested landmarks | Flatten structure | Use single <main>, avoid nesting <section> |
| Skipped by screen reader | Tabindex=-1 | Remove for interactive elements | Don't use tabindex=-1 on buttons/links |
| Modal not announced | Missing role dialog | Add role="dialog" | <div role="dialog" aria-modal="true"> |
| Focus not trapped | Missing focus trap | Implement focus trap | JavaScript focus management |
| Alt text missing | Missing alt attribute | Add alt attribute | <img src="photo.jpg" alt="Team photo"> |
| Heading skipped | Missing heading level | Add all heading levels | Don't skip from h1 to h3 |
| Low contrast | Color fails 4.5:1 ratio | Increase contrast | Change #999 to #595959 on white |
| Form errors not announced | Missing aria-live on errors | Add role="alert" | <div role="alert">{errors}</div> |
---
Resources
**NVDA:**
- Download: https://www.nvaccess.org/download/
- Documentation: https://www.nvaccess.org/documentation/
- Keyboard Commands: https://www.nvaccess.org/files/nvda/documentation/keyCommands.html
**VoiceOver:**
- Built into macOS (Cmd+F5 to enable)
- Apple Documentation: https://www.apple.com/accessibility/voiceover/
- VoiceOver Commands: https://www.apple.com/voiceover/info/guide/
**WCAG 2.1 AA:**
- Quick Reference: https://www.w3.org/WAI/WCAG21/quickref/
- Understanding WCAG: https://www.w3.org/WAI/WCAG21/Understanding/
**Testing Tools:**
- axe DevTools (browser extension): https://www.deque.com/axe/devtools/
- WAVE (browser extension): https://wave.webaim.org/
- Lighthouse (built into Chrome): chrome://lighthouse
---
**Procedure Version:** 1.0
**Last Updated:** 2026-03-22
**Next Review:** 2026-06-22 (Quarterly)
**Owner:** QA Team + Development Team
**Related Documentation:**
docs/accessibility/THREE_TIER_STRATEGY.md(overall strategy)docs/accessibility/VIOLATION_REPORTING.md(violation format)docs/accessibility/MANUAL_TESTING_CHECKLIST.md(checklist)